package com.ldy.seal.util;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.Key;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.util.Date;

import org.apache.commons.io.IOUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import com.ldy.seal.config.SealConfig;
import com.nja.asn1.x500.X500NameBuilder;
import com.nja.asn1.x500.style.BCStyle;
import com.nja.ca.CaInfo;
import com.nja.ca.Config;
import com.nja.ca.P10CertBuilder;
import com.nja.ca.X509CertBuilder;
import com.nja.jce.provider.NjaProvider;
import com.nja.pkcs.PKCS10CertificationRequest;
import com.nja.util.encoders.Hex;

/**
 * 证书管理工具
 * 备注：ROOT、CA的别名都需要是shanghai
 * 
 * @author lindeyi
 *
 */
public final class CertManager {


	Logger logger = LoggerFactory.getLogger(CertManager.class);

	private static final String PASSWORD = "shanghai@626";

	private static final String SERVICE_P12 = "SERVICE.p12";

	private static final String ROOT = "ROOT";
	private static final String CA = "CA";

//	private static final String CER = ".cer";
//	private static final String KEY = ".key";
	private static final String P12 = ".p12";

	private static final String BEGIN_DATE = "BEGIN_DATE";
	private static final String SERIALNO = "SERIALNO";

	private static volatile CertManager manager;

	/**
	 * 根证书
	 */
	private CaInfo rootCert;

	/**
	 * ca证书
	 */
	private CaInfo caCert;

	/**
	 * 配置
	 */
	private SealConfig sealConfig;

	/**
	 * 无参构造函数
	 */
	private CertManager() {
	}

	/**
	 * 获取工具实例
	 * 
	 * @return CertManager
	 */
	public static CertManager getIns() {
		if (null == manager) {
			synchronized (CertManager.class) {
				if (null == manager) {
					manager = new CertManager();
					manager.init();
				}
			}
		}
		return manager;
	}

	/**
	 * 生成用户证书
	 * 
	 * @param p10data
	 * @param userId
	 * @return byte[]
	 */
	public byte[] makeUserCert(String p10data, String userId) {
		try {
			X509CertBuilder param = new X509CertBuilder();
			param.LoadConfig("MGR_USR");
			param.init(caCert);
			param.putp10(p10data);
			param.put(BEGIN_DATE, new Date());
			param.put(SERIALNO, userId);
			CaInfo info = param.GenerateCert();
			return info.getCert().getEncoded();
		} catch (Exception e) {
			logger.error("", e);
			return null;
		}
	}

	/**
	 * 根证书数据
	 * 
	 * @return byte[]
	 * @throws Exception
	 */
	public byte[] getRootCert() throws Exception {
		return rootCert.getCert().getEncoded();
	}

	/**
	 * ca证书数据
	 * 
	 * @return byte[]
	 * @throws Exception
	 */
	public byte[] getCACert() throws Exception {
		return caCert.getCert().getEncoded();
	}

	/**
	 * 初始化，加载或生成根、ca证书
	 */
	private void init() {
		try {
			if (null == manager) {
				logger.error("初始化失败， 请先实例化CerManager");
				return;
			}
			SealConfig configurer = (SealConfig) SpringUtil.getBean(SealConfig.class);
			this.sealConfig = configurer;
			File certSavePath = new File(this.sealConfig.getCertSavePath());
			if (!certSavePath.exists()) {
				certSavePath.mkdirs();
			}
			NjaProvider.AddProvider();
			Config.initial("setting/config.properties");
			// 读取根证书，如果没有则生成
			this.makeRootCert();
			// 读取ca证书，如果没有则生成
			this.makeCACert();
//			// 生成服务端证书
			this.makeServiceCert();
		} catch (Exception e) {
			logger.error("", e);
		}
	}

	/**
	 * 生成根证书
	 */
	private void makeRootCert() {
		try {
			logger.info("Load Root Cert Start...");
			CaInfo info = null;
//			String cerpath = sealConfig.getCertSavePath() + File.separator + ROOT + CER;
//			String keypath = sealConfig.getCertSavePath() + File.separator + ROOT + KEY;
			String cerpath = sealConfig.getCertSavePath() + File.separator + ROOT + P12;
			File root = new File(cerpath);
			if (root.exists()) {
				logger.info("已经存在Root证书，读取证书");
				info = new CaInfo();
				info = loadCertByPKCS12(root, PASSWORD, sealConfig.getRootCert().getAliasName());
//				info.getCert().Load(getBytes(cerpath));
//				info.getKey().Load(getBytes(keypath));
				// CertificateFactory cf = CertificateFactory.getInstance("X.509");
				// X509Certificate x509Certificate = (X509Certificate)
				// cf.generateCertificate(in);
			} else {
				logger.info("生成ROOT证书");
				X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
				builder.addRDN(BCStyle.CN, sealConfig.getRootCert().getCommonName());
				builder.addRDN(BCStyle.OU, sealConfig.getRootCert().getUnitName());
				builder.addRDN(BCStyle.C, "CN");

				P10CertBuilder pc10 = new P10CertBuilder();
				pc10.LoadConfig("MGR_RTCA_REQ");
				pc10.init();
				pc10.PutSubject(builder.build());
				PKCS10CertificationRequest certBytes = pc10.BuildPKCS10();

				X509CertBuilder param = new X509CertBuilder();
				param.LoadConfig("MGR_ROOTCA");
				param.init(pc10.GetCurInfo());
				param.put(BEGIN_DATE, new Date());
				param.put(SERIALNO, new BigInteger(1, Hex.decode(sealConfig.getRootCert().getSerialNumber())));
				param.putp10(certBytes.getEncoded());

				info = param.GenerateCert();
				// 加载私钥
				info.getKey().Load(pc10.GetCurInfo().getKey().getPrivateKey().getEncoded());

				logger.info("Make Root Cert: {}", info.getCert());
				// 保存根证书
//				info.getCert().Save(cerpath);
//				info.getKey().Save(keypath);
				// 保存pkcs12
				savePKCS12(sealConfig.getCertSavePath() + File.separator + ROOT + P12, PASSWORD, //
						info.getKey().getPrivateKey(), sealConfig.getRootCert().getAliasName(), info.getCert().getCert());
			}
			rootCert = info;
		} catch (Exception e) {
			logger.error("", e);
		}
	}

	/**
	 * 生成ca证书
	 */
	private void makeCACert() {
		try {
			logger.info("Load CA Cert Start...");

			CaInfo info = null;
//			String cerpath = sealConfig.getCertSavePath() + File.separator + CA + CER;
//			String keypath = sealConfig.getCertSavePath() + File.separator + CA + KEY;
			String cerpath = sealConfig.getCertSavePath() + File.separator + CA + P12;
			File ca = new File(cerpath);
			if (ca.exists()) {
				logger.info("已经存在CA证书，读取证书");
				info = loadCertByPKCS12(ca, PASSWORD, sealConfig.getCaCert().getAliasName());
//				info.getCert().Load(getBytes(cerpath));
//				info.getKey().Load(getBytes(keypath));
			} else {
				logger.info("生成CA证书");
				X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
				builder.addRDN(BCStyle.CN, sealConfig.getCaCert().getCommonName());
				builder.addRDN(BCStyle.OU, sealConfig.getCaCert().getUnitName());
				builder.addRDN(BCStyle.C, "CN");
				P10CertBuilder pc10 = new P10CertBuilder();
				pc10.LoadConfig("MGR_C2P10");
				pc10.init(rootCert);
				pc10.PutSubject(builder.build());
				PKCS10CertificationRequest certBytes = pc10.BuildPKCS10();

				X509CertBuilder param = new X509CertBuilder();
				param.LoadConfig("MGR_CA2");
				param.init(rootCert);
				param.put(BEGIN_DATE, new Date());
				param.put(SERIALNO, new BigInteger(1, Hex.decode(sealConfig.getCaCert().getSerialNumber())));

				param.putp10(certBytes.getEncoded());
				info = param.GenerateCert();
				logger.info("Make CA Cert:{}", info.getCert());
				info.getKey().Load(pc10.GetCurInfo().getKey().getPrivateKey().getEncoded());
//				info.getCert().Save(cerpath);
//				info.getKey().Save(keypath);
				// 保存pkcs12
				savePKCS12(sealConfig.getCertSavePath() + File.separator + CA + P12, PASSWORD, //
						info.getKey().getPrivateKey(),  sealConfig.getCaCert().getAliasName(), info.getCert().getCert());
			}
			caCert = info;

		} catch (Exception e) {
			logger.error("", e);
		}
	}

	/**
	 * 生成服务端证书
	 */
	public void makeServiceCert() {
		String filepath = sealConfig.getCertSavePath() + File.separator + SERVICE_P12;
		try {
			File file = new File(filepath);
			if (file.exists()) {
				logger.info("已经存在服务端证书");
				return;
			}
			X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
			builder.addRDN(BCStyle.CN, sealConfig.getServiceCert().getCommonName());
			builder.addRDN(BCStyle.OU, sealConfig.getServiceCert().getUnitName());
			X509CertBuilder param = new X509CertBuilder();
			param.LoadConfig("MGR_SOFT");
			param.init(caCert);
			param.put(BEGIN_DATE, new Date());
			param.put(SERIALNO, sealConfig.getServiceCert().getSerialNumber());
			param.PutSubject(builder.build());
			param.GenerateCert();
			byte[] certData = param.getP12("cert", "key", sealConfig.getCertPassword());

			IOUtils.write(certData, new FileOutputStream(filepath));
			logger.info("生成服务端证书");
		} catch (Exception e) {
			logger.error("", e);
		}
	}

	/**
	 * 获取服务器证书
	 * 
	 * @return InputStream
	 * @throws IOException
	 */
	public InputStream getServiceCert() throws IOException {
		String filepath = sealConfig.getCertSavePath() + File.separator + SERVICE_P12;
		return new FileInputStream(filepath);
	}

	/**
	 * 获取文件转byte[]
	 * 
	 * @param filePath
	 * @return byte[]
	 */
	public static byte[] getBytes(String filePath) {
		byte[] buffer = null;
		try {
			File file = new File(filePath);
			FileInputStream fis = new FileInputStream(file);
			ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);
			byte[] b = new byte[1000];
			int n;
			while ((n = fis.read(b)) != -1) {
				bos.write(b, 0, n);
			}
			fis.close();
			bos.close();
			buffer = bos.toByteArray();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return buffer;
	}

	/**
	 * 保存pkcs12证书文件
	 * 
	 * @param path
	 * @param password
	 * @param alias
	 * @param key
	 * @param certs
	 */
	public void savePKCS12(String path, String password, Key key, String alias, Certificate... certs) {
		if (!StringUtils.hasLength(alias)) {
			alias = "shanghai";
		}
		try (ByteArrayOutputStream out = new ByteArrayOutputStream(); OutputStream fos = new FileOutputStream(path)) {
			KeyStore keyStore = KeyStore.getInstance("PKCS12", new BouncyCastleProvider());
			keyStore.load(null, null);
			keyStore.setKeyEntry(alias, key, password.toCharArray(), certs);
			keyStore.store(out, password.toCharArray());
			byte[] keyStoreData = out.toByteArray();
			fos.write(keyStoreData);
			fos.flush();
		} catch (Exception e) {
			logger.error("存储pkcs12证书文件失败: {}", e.getMessage());
		}
	}

	/**
	 * 加载证书通过pkcs12文件
	 * 
	 * @param cert
	 * @param password
	 * @param alias
	 * @return
	 * @throws Exception
	 */
	public CaInfo loadCertByPKCS12(File cert, String password, String alias) throws Exception {
		if (!StringUtils.hasLength(alias)) {
			alias = "shanghai";
		}
		CaInfo info = new CaInfo();
		KeyStore keyStore = KeyStore.getInstance("PKCS12", new BouncyCastleProvider());
		try (FileInputStream io = new FileInputStream(cert)) {
			keyStore.load(io, password.toCharArray());
			Certificate certificate = keyStore.getCertificate(alias);
			info.getCert().Load(certificate.getEncoded());
			Key key = keyStore.getKey(alias, password.toCharArray());
			info.getKey().Load(key.getEncoded());
		}
		return info;
	}
}
